package com.moonshineproject.plugin.actionscript.as3parser.swcparser
{
	import __AS3__.vec.Vector;
	
	import com.codeazur.as3abc.ABC;
	import com.codeazur.as3abc.data.Method;
	import com.codeazur.as3abc.data.NominalType;
	import com.codeazur.as3abc.data.Parameter;
	import com.codeazur.as3abc.data.multinames.ABCQName;
	import com.codeazur.as3abc.data.traits.ITrait;
	import com.codeazur.as3abc.data.traits.TraitGetter;
	import com.codeazur.as3abc.data.traits.TraitMethod;
	import com.codeazur.as3abc.data.traits.TraitSetter;
	import com.codeazur.as3abc.data.traits.TraitSlot;
	import com.codeazur.as3swf.SWF;
	import com.codeazur.as3swf.tags.ITag;
	import com.codeazur.as3swf.tags.TagDoABC;
	import com.moonshineproject.plugin.actionscript.as3parser.completers.AccessLevels;
	import com.moonshineproject.plugin.actionscript.as3parser.parsers.AS3ParserUtil;
	import com.moonshineproject.plugin.actionscript.as3parser.vo.ClassVO;
	import com.moonshineproject.plugin.actionscript.as3parser.vo.FunctionVO;
	import com.moonshineproject.plugin.actionscript.as3parser.vo.MutatorVO;
	import com.moonshineproject.plugin.actionscript.as3parser.vo.PackageVO;
	import com.moonshineproject.plugin.actionscript.as3parser.vo.ParameterVO;
	import com.moonshineproject.plugin.actionscript.as3parser.vo.VariableVO;
	
	import deng.fzip.FZip;
	import deng.fzip.FZipFile;
	
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;
	
	public class SWCParser
	{
		protected var topPackage:PackageVO;
		
		// Used for method assignment
		protected var lastClass:ClassVO;
		// Used for getter/setter assignment
		protected var mutators:Vector.<MutatorVO>;
		
		public function SWCParser()
		{
			topPackage = new PackageVO();
			topPackage.name = "";
		}
		
		public function parseSWC(file:File):PackageVO
		{
			// Copy swc to tmp storage
			var tmpStorage:File = File.createTempDirectory();
			file.copyTo(tmpStorage.resolvePath(file.name), true);
			
			var copiedFile:File = tmpStorage.resolvePath(file.name);
			
			// Read copied file
			var fs:FileStream = new FileStream();
			fs.open(copiedFile, FileMode.READ);
			
			var bt:ByteArray = new ByteArray();
			fs.readBytes(bt);
			
			// Kick off the parsing
			swfLoaded(bt);
			
			fs.close();
			
			return topPackage;
		}
		
		protected function swfLoaded(data:ByteArray):void
		{
			var now:int = flash.utils.getTimer();
			
			// Unzip the swc
			var fzip:FZip = new FZip();
			fzip.loadBytes(data);
			
			// Get the library.swf containing the juicy stuff
			var zippedFile:FZipFile = fzip.getFileByName("library.swf");
			
			var content:ByteArray = zippedFile.content;
			
			// Parse SWF file
			var swf:SWF = new SWF(content);
			var tag:ITag;
			var abc:ABC;
           
			// Loop SWF content looking for ABC
			var i:int, len:int = swf.tags.length;
			for (i = 0; i < len; i++)
			{
				tag = swf.tags[i];
				if (tag is TagDoABC)
				{	
					// Parse ABC
					abc = new ABC(TagDoABC(tag).bytes);
					
					// Find classes & their methods
					for each (var c:NominalType in abc.types)
					{
						// Assume all methods in this ABC Tag thingie will belong to this class.
						// This might be very wrong.
						lastClass = parseClass(c);
						mutators = new Vector.<MutatorVO>();
						if (!lastClass) continue;

						if (lastClass.name == "swctest")
						{
							trace("debugtime");
						}

						for each (var t:ITrait in c.instanceTraits)
						{
							if (t is TraitMethod)
							{
								parseMethod(TraitMethod(t).method);
							}
							else if (t is TraitSlot)
							{
								parseVariable(TraitSlot(t));
							}
							else if (t is TraitSetter)
							{
								parseSetter(TraitSetter(t));
							}
							else if (t is TraitGetter)
							{
								parseGetter(TraitGetter(t));
							}
						}
					}				
				} // if
			}
			
			trace('SWC Parse Time: ' + (flash.utils.getTimer() - now) + 'ms');
		}

		
		protected function parseClass(type:NominalType):ClassVO
		{
			if (!type.name) return null;
			
			if(type.name.name.length > 70)
			{
				// Assume some oddity which we don't like. Example:
				// _7499a393d49844c4379f787e56145d438c1baa1143eda854f2286ede1144c161_flash_display_Sprite
				// TODO: Double check if name contains this pattern, if not we shouldn't return 
				return null;
			}

			var c:ClassVO = new ClassVO();
			c.name = type.name.name;
			
			if (type.base && type.base['name'])
			{
				var ext:String = type.base['name'];
				if (type.base['abcNamespace'].name)
				{
					ext = type.base['abcNamespace'].name + "." +ext;
				}
				
				c.extendsClass = ext;
			}
			
			var p:PackageVO = AS3ParserUtil.findOrCreatePackage(type.name.abcNamespace.name, topPackage);
			p.addClass(c);
			
			return c;	
		}
		
		protected function parseMethod(method:Method):void
		{
			if (!method.name) return;
			
			var split:Array = method.name.split("/");
			var pkg:String = split[0];
			var name:String = split[1];
			var accessor:String;
			
			var nameSplit:Array = name.split(":");
			if (nameSplit.length == 2)
			{
				accessor = nameSplit[0];
				if (accessor.split(".").length > 1) {
					accessor = "internal";
				}
				
				name = nameSplit[1];
			}
			else if (nameSplit.length > 2)
			{
				name = nameSplit[nameSplit.length-1];
			}
			
			var func:FunctionVO = new FunctionVO();
			func.name = name;
			
			// Never any need for private methods since we can't show them
			if (accessor == "private") { return; }
			
			if (!accessor || accessor == "AS3") func.accessor = AccessLevels.PUBLIC;
			else func.accessor = AccessLevels.stringToLevel(accessor);
			
			if (method.parameters)
			{
				func.parameters = parseParams(method.parameters);
			}
			
			if (method.returnType)
			{
				if (method.returnType is ABCQName)
				{
					func.type = ABCQName(method.returnType).name;
					if (ABCQName(method.returnType).abcNamespace)
					{
						var retpkg:String = ABCQName(method.returnType).abcNamespace.name;
						if (pkg) func.typePackage = retpkg;
					}
				}
			}
			
			lastClass.members.push(func);	
		}
		
		protected function parseParams(params:Vector.<Parameter>):Vector.<ParameterVO>
		{
			var toRet:Vector.<ParameterVO> = new Vector.<ParameterVO>();
			
			var param:Parameter;
			for (var i:int = 0; i < params.length; i++)
			{
				param = params[i];
				var p:ParameterVO = new ParameterVO();
				p.name = param.name;
				
				if (param.type && param.type is ABCQName)
				{
					p.type = ABCQName(param.type).name;
					if (ABCQName(param.type).abcNamespace)
					{ 
						var pkg:String = ABCQName(param.type).abcNamespace.name;
						if (pkg && pkg != "")
						{
							p.typePackage = p.type;
						}
					}	
				}
				
				toRet[i] = p;
			}
			
			return toRet;
		}
		
		protected function parseGetter(v:TraitGetter):void
		{
			var mutator:MutatorVO;
			var name:String = v.name.name;
			
			for (var i:int = 0; i < mutators.length; i++)
			{
				if (mutators[i].name == name)
				{
					mutator = mutators[i];
					break;
				}
			}
			
			if (!mutator)
			{
				mutator = new MutatorVO();
				mutator.name = name;
				
				mutators.push(mutator);
				lastClass.members.push(mutator);
				
				if (v.name.abcNamespace)
				{
					var kind:int = v.name.abcNamespace.kind;
					mutator.accessor = getAccessLevelFromSWCKind(kind);
				}
			}
			
			mutator.getter = parseGetterSetter(v.method);
		}
		
		protected function parseSetter(v:TraitSetter):void
		{
			var mutator:MutatorVO;
			var name:String = v.name.name;
			
			for (var i:int = 0; i < mutators.length; i++)
			{
				if (mutators[i].name == name)
				{
					mutator = mutators[i];
					break;
				}
			}
			
			if (!mutator)
			{
				mutator = new MutatorVO();
				mutator.name = name;
				
				mutators.push(mutator);
				lastClass.members.push(mutator);
				
				if (v.name.abcNamespace)
				{
					var kind:int = v.name.abcNamespace.kind;
					mutator.accessor = getAccessLevelFromSWCKind(kind);
				}
			}
			
			mutator.setter = parseGetterSetter(v.method);
		}
		
		protected function parseGetterSetter(method:Method):FunctionVO
		{
			var func:FunctionVO = new FunctionVO();
			
			var split:Array = method.name.split("/");
			var pkg:String = split[0];
			var name:String = split[1];
			var accessor:String;
			
			var nameSplit:Array = name.split(":");
			if (nameSplit.length == 2)
			{
				name = nameSplit[1];
			}
			else if (nameSplit.length > 2)
			{
				name = nameSplit[nameSplit.length-1];
			}
			
			func.name = name;
			
			if (method.returnType)
			{
				if (method.returnType is ABCQName)
				{
					func.type = ABCQName(method.returnType).name;
					if (ABCQName(method.returnType).abcNamespace)
					{
						var retpkg:String = ABCQName(method.returnType).abcNamespace.name;
						if (pkg) func.typePackage = retpkg;
					}
				}
			}
			if (method.parameters)
			{
				func.parameters = parseParams(method.parameters);
			}
			
			return func;
		}
		
		protected function parseVariable(v:Object):void // is really TraitSlot
		{
			var vo:VariableVO = new VariableVO();
			vo.name = v.name.name;
			
			if (v.name.abcNamespace)
			{
				var kind:int = v.name.abcNamespace.kind;
				vo.accessor = getAccessLevelFromSWCKind(kind);
			}
			
			if (v.type is ABCQName)
			{
				vo.type = v.type.name;
				if (v.type.abcNamespace 
					&& v.type.abcNamespace.name)
				{
					vo.type += "." + v.type.abcNamespace.name;
				}
			}
			
			lastClass.members.push(vo);
		}
	
		protected function getAccessLevelFromSWCKind(kind:int):int
		{
			// Magic numbers found by testing against a known SWC
			return  (kind == 23) ? AccessLevels.INTERNAL :
					(kind == 5)  ? AccessLevels.PRIVATE :
					(kind == 22) ? AccessLevels.PUBLIC :
					(kind == 24) ? AccessLevels.PROTECTED :
					AccessLevels.PUBLIC;
		}
		
	}
}